<?php


class koi_apply {
    var $response = array();
    
    function end() {
        header('Content-type: text/json');
        $last = '{';
        foreach ($this->response as $var => $value) {
            echo $last;
            if ($var=='JSON') {
                $last = $value;
            } else {
                $last = "\n" . '    \'' . addslashes($var) . '\':';
                if (is_string($value)) {
                    $last .= '\'' . addslashes($value) . '\',';
                } elseif (is_array($value)) {
                    $value = array_map("addslashes", $value);
                    $last .= '[\'' . implode('\',\'',$value) . '\'],';
                } else {
                    $last .= var_export($value,true) . ',';
                }
            }
        }
        echo substr($last,0,(strlen($last)-1));
        echo "\n".'}';
        exit;
    }
    
    function enderr($string) {
        $this->addvar('error',$string);
        $this->addvar('request','error');
        $this->end();
    }
    
    function addvar($var,$value) {
        $this->response[$var] = $value;
    }
    
    // Load-balance mirrors
    function mirrors() {
        $arr = array();
        $mirrors = $GLOBALS['mirrors'];
        $weight = array_sum($mirrors);
        foreach ($mirrors as $k => $m) $mirrors[$k] = ceil(($m/$weight)*100);

        asort($mirrors);
        // Only bother to order the first mirror
        while (empty($arr)) {
            if (!next($mirrors)) reset($mirrors);
            if (rand(0,100) <= current($mirrors)) {
                $m = key($mirrors);
                unset($mirrors[key($mirrors)]);
                $arr = array_keys($mirrors);
                array_unshift($arr,$m);
            }
        }

        return $arr;
    }
    
    function download_curl($url,$stream,$suppress=false) {
        if (!function_exists('curl_init')) {
            if (!$suppress) $this->enderr('cURL is not supported. Refresh to select another download method');
            return false;
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_FAILONERROR, 1);
        
        if ($stream) {
            curl_setopt($ch, CURLOPT_FILE, $stream);
        } else curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        
        $r = curl_exec($ch);
        if ( (!$r) && (!$supress) ) $this->addvar('curl',curl_error($ch));
        curl_close($ch);
        return $r;
    }
    
    function download_fopen($url,$stream,$suppress=false) {
        if (!$file = @fopen($url,'rb')) {
            if (!$suppress) $this->enderr('fopen is not supported. Refresh to select another download method');
            return false;
        }
        
        if ($stream) {
            $r = true;
            while ($block = fread($file,512)) fwrite($stream,$block);
        } else {
            $r = '';
            while ($block = fread($file,512)) $r .= $block;
        }
        
        fclose($file);
        return $r;
    }
    
    function download_fsock($url,$stream,$suppress=false) {
        $data = parse_url($url);
        
        if (!$connect = @fsockopen($data['host'],80,$errno,$errstr)) {
            if (!$suppress) $this->addvar('fsock',$errstr);
            return false;
        }
        
        fwrite($connect, "GET ".$data['path']." HTTP/1.1\r\n");
        fwrite($connect, "Host: ".$data['host']."\r\n");
        fwrite($connect, "Connection: Close\r\n\r\n");
        
        // Skip HTTP headers
        $stop = false;
        while (!$stop) {
            $r = str_replace("\r",'',fgets($connect));
            if (strpos($r,'404 Not Found')) {
                if (!$supress) $this->addvar('fsock','404');
                return false;
            }
            if ($r == "\n") $stop = true;
        }
        
        if ($stream) {
            $r = true;
            while(!feof($connect)) fwrite($stream,fread($connect,512));
        } else {
            $r = '';
            while(!feof($connect)) $r .= str_replace("\r",'',fread($connect,512));
        }
        fclose($connect);
        
        return $r;
    }
    
    function pull_file($file,$hash=false,$gz=false) {
        if (!$GLOBALS['settings']['gz']) $gz=false;
        
        $arr = array_keys($GLOBALS['mirrors']);
        $url = $arr[($_GET['mirror']-1)] . $file . ($gz?'.gz':'');
        
        
        if (is_file('./'.$file)) @unlink('./'.$file);
        if ( ($gz) & (is_file('./'.$file.'.gz')) ) @unlink('./'.$file.'.gz');
        if (!$stream = fopen('./'.$file.($gz?'.gz':''),'wb')) {
            $this->enderr('Could not save downloaded file(' . $file . '). Is this directory writable?');
            return false;
        }
        switch($_GET['method']) {
            case 'curl': $r = $this->download_curl($url,$stream); break;
            case 'fopen': $r = $this->download_fopen($url,$stream); break;
            case 'fsock': $r = $this->download_fsock($url,$stream); break;
            case 'no': $this->enderr('Offline install method active but can\'t find the required file: ' . $file); break;
            default: $this->enderr('Download method not recognized'); break;
        }
        fclose($stream);
        
        if (!$r) {
            $this->enderr('Download using ' . $_GET['method'] . ' method failed! File unavaliable: ' . $file . ($gz?'.gz':'') . ' Please try again later or refresh and select a different download mirror or download method');
            return false;
        }
        
        if ($gz) {
            $gzfile = gzopen('./'.$file.'.gz','rb');
            $ofile = fopen('./'.$file,'wb');
            while ($block = gzread($gzfile,512)) fwrite($ofile,$block);
            gzclose($gzfile);
            fclose($ofile);
            @unlink('./'.$file.'.gz');
        }
        
        if ( ($hash) && ($hash != md5_file('./'.$file)) ) {
            $this->enderr('Could not validate downloaded file(' . $file . '). MD5 hash does not match. Please try again later or refresh and select a different download mirror or download method');
            unlink('./'.$file);
            return false;
        }
        
        $this->addvar($file,'downloaded'. ($gz?' [gz]':''));
        return true;
    }
    
    
    
    function mimic_unpak($file) {
        koi_import('libtar.php');
        $tar = new tar('./'.$file);
        
        $folders = array();
        $first = true;
        while ( ($first) || ($tar->next()) ) {
            $first = false;
            $file = $tar->data();
            if ($file['type']==5) {
                if (substr($file['name'],0,1)=='/') $file['name'] = substr($file['name'],1);
                if (substr($file['name'],-1)!='/') $file['name'] .= '/';
                
                $level = substr_count($file['name'],'/');
                if (!is_array($folders[$level])) $folders[$level] = array();
                $folders[$level][] = array($file['name'],$file['mode']);
            }
        }
        ksort($folders);
        foreach ($folders as $level) {
            foreach ($level as $folder) {
                if (!is_dir($folder[0])) {
                    if (is_file($folder[0])) $this->enderr('Folder to write to already exists as a file: ' . $folder[0] . ' Please correct this error then refresh to retest');
                    @mkdir($folder[0]);
                    if (!is_dir($folder[0])) {
                        @chmod(substr($folder[0],0,strrpos($folder[0],'/',1)),0777);
                        @mkdir($folder[0]);
                        if (!is_dir($folder[0])) $this->enderr('Folder creation failed - ' . $folder[0]);
                    }
                    @chmod($folder[0],$folder[1]);
                    
                } else @chmod($folder[0],$folder[1]);
                if (!is_writable($folder[0])) $this->enderr('Folder just created is not writable - ' . $folder[0]);
            }
        }
        
        $new = array();
        $overwrite = array();
        
        $tar->seek(0);
        $first = true;
        while ( ($first) || ($tar->next()) ) {
            $first = false;
            $file = $tar->data();
            if ($file['type']==0) {
                if (is_file($file['name'])) {
                    if (is_writable($file['name'])) {
                        $overwrite[] = $file['name'];
                    } else $this->enderr('File to write to already exists and is not writable - ' . $file['name']);
                } else {
                    if (is_writable('./'.substr($file['name'],0,strrpos($file['name'],'/')))) {
                        $new[] = $file['name'];
                    } else $this->enderr('Folder to write to is not writable - ' . substr($file['name'],0,strrpos($file['name'],'/')));
                }
            }
        }
        
        $tar->close();
        
        $this->addvar('newwrite',$new);
        $this->addvar('overwrite',$overwrite);
    }
    
    
    
    function unpak($tarfile) {
        koi_import('libtar.php');
        $tar = new tar('./'.$tarfile);
        
        $unpacked = array();
        $first = true;
        while ( ($first) || ($tar->next()) ) {
            $first = false;
            $file = $tar->data();
            if ($file['type']==0) {
                $fhandle = fopen('./' . $file['name'],'wb');
                fwrite($fhandle,$tar->curfile());
                fclose($fhandle);
                @chmod($file['name'],$file['mode']);
                $unpacked[] = $file['name'] . ' (' . $file['mode'] . ')';
            }
        }
        $tar->close();
        
        $this->addvar('unpacked',$unpacked);
        unlink('./' . $tarfile);
    }
    
    
    
    function switchboard($stage) {
        $_GET['action'] = (isset($_GET['action'])?urlencode($_GET['action']):'install');
        switch($stage) {
            case 'cleanup':
                if ( (!$GLOBALS['settings']['keeplock']) && (@unlink('./lock.php')) ) $this->addvar('lock.php','deleted');
                if (@unlink('./download.php')) {
                    $this->addvar('download.php','deleted');
                } elseif (is_file('./download.php')) {
                    $this->addvar('download.php','not deleted!');
                    $this->addvar('fail','download.php was not deleted successfuly! Leaving download.php there is a major security risk - please delete download.php');
                }
                break;
            case 'install':
                $this->unpak($_GET['file']);
                break;
            case 'scan':
                $this->mimic_unpak($_GET['file']);
                break;
            case 'download':
                if ( (is_file('./' . $_GET['file'])) && (md5_file('./' . $_GET['file']) == $_GET['hash']) ) {
                    $this->addvar('cache',true);
                } else {
                    $this->pull_file($_GET['file'],$_GET['hash'],function_exists('gzfile'));
                }
                break;
            case 'refresh':
                $this->pull_file('download.php');
                break;
            case 'filecheck':
                if (is_file('./' . $_GET['action'] . '.lst')) {
                    header('Content-type: text/json');
                    $pull = implode(file('./' . $_GET['action'] . '.lst'));
                    $this->addvar('JSON',substr($pull,1,strlen($pull)-3).',');
                    $this->addvar('method','no');
                } else {
                    $pull = '';
                    
                    if ($_GET['mirror']) {
                        $arr = array_keys($GLOBALS['mirrors']);
                        $arr = array($arr[($_GET['mirror']-1)]);
                    } else $arr = $this->mirrors();
                    
                    foreach ($arr as $i => $mirror) {
                        if (substr($pull,0,1)!='{') {
                            $url = $mirror . $_GET['action'] . '.lst';
                            switch ($_GET['method']) {
                                case 'no': $pull = false; $method = 'no'; break;
                                case 'curl': $pull = $this->download_curl($url,false,true); break;
                                case 'fopen': $pull = $this->download_fopen($url,false,true); break;
                                case 'fsock': $pull = $this->download_fsock($url,false,true); break;
                                default:
                                    $pull = $this->download_curl($url,false,true);
                                    if (substr($pull,0,1)!='{') {
                                        $pull = $this->download_fopen($url,false,true);
                                        if (substr($pull,0,1)!='{') {
                                            $pull = $this->download_fsock($url,false,true);
                                            if (substr($pull,0,1)=='{') $method = 'fsock';
                                        } else $method = 'fopen';
                                    } else $method = 'curl';
                                    break;
                            }
                        }
                    }
                    if (substr($pull,0,1)!='{') {
                        $this->addvar('method','no');
                    } else {
                        $this->addvar('JSON',substr($pull,1,strlen($pull)-3).',');
                        $this->addvar('method',($method?$method:$_GET['method']));
                        $this->addvar('mirror',($i+1));
                    }
                }
                break;
            default:
                $this->enderr('Stage given not recognized: ' . $_GET['stage']);
                break;
        }
    }
}



$koi = new koi_apply;

// Safety lock
if (is_file('./lock.php')) {
    $lock = file('./lock.php');
    if ( (rtrim($lock[0])=='<?php exit; ?' . '>') && ($_SERVER['REMOTE_ADDR']!=$lock[1]) ) {
        $koi->enderr('For security this file is locked down to the original user only. To unlock this file FTP to your site and delete lock.php from this folder.');
    }
}

$koi->switchboard($_GET['stage']);
$koi->end();

?>